#include <gtk/gtk.h>
#include "CycleTimer.h"
#include <stdio.h>
#include <time.h>
#include <glib/gprintf.h>
#include <string.h>
#include <stdlib.h>
#include <gdk/gdk.h>
#include "visualizer.h"

#define num_of_char_at_bottom 100
#define IMG_H 20
#define IMG_W 1024
#define stack_size 10000
#define zoom_factor 2.f
#define num_of_color 6
#define chun_thred 100

typedef unsigned long long ULL;

ULL max_freq;
ULL len_chrom;

int zoomed_to_bottom;
ULL* chrom_data;
char* dna_strand;

unsigned char d2_color_map[num_of_color][3];

unsigned char color_map[num_of_color*3];

unsigned char pix_data[IMG_W*3];
double avg_value[IMG_W];

int nX,nY;
ULL bg, ed;
GdkPixbuf *pb;
GtkWidget *window,*vbox,*hbox, *zoom, *event_box, *out_hbox, *right_vbox;
GtkWidget *legend_box[num_of_color];
GtkWidget* bot_hbox;
GtkLabel *bottom_label, *legend_label[num_of_color], *left_label, *right_label;
GtkImage *im, *legend_color[num_of_color];
ULL d_num_byte;
ULL r_num_byte;
int map_num_byte;

typedef struct{
	int top;
	ULL bg_stack[stack_size];
	ULL ed_stack[stack_size];
} pos_stack;

pos_stack st;

__device__ ULL* device_chrom_data;
__device__ unsigned char* device_pix_value;
__device__ unsigned char* device_base_pix_value;
__device__ __constant__ unsigned char* device_color_map;

void init_mem(){

    cudaMalloc((void**)&device_chrom_data,d_num_byte);
    cudaMalloc((void**)&device_pix_value,r_num_byte);
    cudaMalloc((void**)&device_color_map,map_num_byte);
    cudaMemcpy(device_chrom_data, chrom_data, d_num_byte, cudaMemcpyHostToDevice);
    cudaMemcpy(device_color_map, color_map, map_num_byte, cudaMemcpyHostToDevice);

}

__global__ void
kernel_small_scale(ULL bg, ULL ed, ULL chunksize, ULL* input, unsigned char* output , ULL max_freq, unsigned char* color_map) {
   ULL index = blockIdx.x * blockDim.x + threadIdx.x, i;

    double numer=1.0;
    numer*=chunksize;
    double s=0.0;
    for (ULL i=index*chunksize; i<(index+1)*chunksize && i<ed; i++)
	s+=(double)input[i]/chunksize;

    int m=(int)(s*num_of_color)/max_freq;
    if (m>=num_of_color)
  	m=num_of_color-1;	

    for (i=0; i<3; i++){
 	output[index*3+i]=color_map[m*3+i];				
   }  

   return ;
}



void launch_kernal(ULL bg, ULL ed, ULL chunk_len){
    const int threadsPerBlock = 16;
    const int blocks = (IMG_W + threadsPerBlock - 1) / threadsPerBlock;
	for (int i=0; i<IMG_W; i++)
	    kernel_small_scale<<<blocks, threadsPerBlock>>>(bg,ed,chunk_len,device_chrom_data, device_pix_value+i*3, max_freq, device_color_map);
}

void update_pix_data(){
    ULL i=bg,k;
    ULL chunk_len=(ed-bg)/IMG_W;

/*

*/


	cudaError_t err = cudaGetLastError();
    const int threadsPerBlock = 32;
    const int blocks = (IMG_W + threadsPerBlock - 1) / threadsPerBlock;
  
    kernel_small_scale<<<blocks, threadsPerBlock>>>(bg,ed,chunk_len,device_chrom_data, device_pix_value, max_freq, device_color_map);
  
	if (err != cudaSuccess) 
	    printf("Error: %s\n", cudaGetErrorString(err));

	cudaMemcpy(pix_data, device_pix_value, r_num_byte, cudaMemcpyDeviceToHost);
	
	
	
	
}

void update_image()
{
  int ht,wt;
  int i=0,j=0;
  int rowstride=0;  
  int bpp=0;
  guchar *pixel;

  bpp=3;	   
  ht=IMG_H;   
  wt=IMG_W;	  
  pixel=gdk_pixbuf_get_pixels(pb);
  rowstride=wt*bpp;
 

  double startTime = CycleTimer::currentSeconds();
  update_pix_data();
  for(i=0;i<ht;i++)		
	memcpy(pixel+i*rowstride,pix_data,rowstride); 
 double endTime= CycleTimer::currentSeconds();
  double overallDuration = endTime - startTime;
  printf("Overall: %.3f ms\n", 1000.f * overallDuration);
  
  gtk_image_set_from_pixbuf(im,pb);

  return;
}

void update_label(){
	  char str1[10], str2[10];
	  sprintf(str1,"%d",bg);
	  gtk_label_set_text(left_label,str1);
	  sprintf(str2,"%d",ed);
	  gtk_label_set_text(right_label,str2);
}




void zoom_to_bottom(ULL bg, ULL ed){
	return;
	char text[num_of_char_at_bottom];
	ULL i;
	for (i=0; i<num_of_char_at_bottom; i++)
		text[i]=chrom_data[bg+i];
	gtk_label_set_text(bottom_label, text);

	gtk_container_remove (GTK_CONTAINER(event_box), (GtkWidget*)im);
	gtk_widget_unparent((GtkWidget*)bottom_label);
	gtk_container_add(GTK_CONTAINER(event_box),(GtkWidget*)bottom_label);

}

void zoom_out(){
	if (st.top==-1)
		return;
	bg=st.bg_stack[st.top];
	ed=st.ed_stack[st.top];
	st.top--;
	update_image();
	update_label();
}

void zoom_in(gdouble x){
	double norm_loc=x/IMG_W;
	ULL len=ed-bg;	
	ULL center=bg+(ULL)(len*norm_loc);
	ULL new_len=(ULL) (len/zoom_factor);

	if (new_len<=IMG_W){
		ULL dummy_bg=center-(new_len)/2 >=0 ? center-(new_len)/2 : 0;
		ULL dummy_ed=center+(new_len)/2 <len_chrom ? center+(new_len)/2 : len_chrom;
		zoom_to_bottom(dummy_bg,dummy_ed);
		return;   
	}

	
	st.top++;
	st.bg_stack[st.top]=bg;	
	st.ed_stack[st.top]=ed;
	if (center<(new_len)/2+bg)
		bg=bg;
	else bg=center-(new_len)/2;
	ed=center+(new_len)/2 <ed ? center+(new_len)/2 : ed;
	update_image();
	update_label();
}

static gint button_press_event(GtkWidget * widget,
          GdkEventButton * event, gpointer data)
{
        if (event->button == 1)      
        { 
		zoom_in(event->x);
	      				
        }
		else if (event->button == 3)
		{
			zoom_out();
		}
        return TRUE;
}

void fill_pixbuf(int color, GdkPixbuf *temp){
  int ht,wt;
  int i=0,j=0;
  int rowstride=0;  
  int bpp=3;
  guchar *pixel;
 

  bpp=3;	   

  int n_channels = gdk_pixbuf_get_n_channels (temp);

  g_assert (gdk_pixbuf_get_colorspace (temp) == GDK_COLORSPACE_RGB);
  g_assert (gdk_pixbuf_get_bits_per_sample (temp) == 8);
  g_assert (!gdk_pixbuf_get_has_alpha (temp));
  g_assert (n_channels==3);

  ht=gdk_pixbuf_get_height(temp);
 
  wt=gdk_pixbuf_get_width(temp);

  pixel=gdk_pixbuf_get_pixels(temp);

  rowstride=wt*bpp;

  for(i=0;i<ht;i++)		
    for(j=0;j<rowstride;j+=bpp)   
      {	
    	pixel[i*rowstride + j+0]=d2_color_map[color][0];
    	pixel[i*rowstride + j+1]=d2_color_map[color][1];
    	pixel[i*rowstride + j+2]=d2_color_map[color][2];
      }
  return;
}


void quit(){
    cudaFree(device_chrom_data);
    cudaFree(device_pix_value);
    cudaFree(device_color_map);
   free(chrom_data);
   gtk_main_quit();
}

void set_up_window()
{

  //event box and image
  pb=gdk_pixbuf_new(GDK_COLORSPACE_RGB,0,8,IMG_W,IMG_H);
  im=(GtkImage*) gtk_image_new_from_pixbuf(pb);

//  gtk_widget_set_valign(im,GTK_ALIGN_END);
  update_image();
  event_box = gtk_event_box_new ();
//  gtk_widget_set_valign(event_box,GTK_ALIGN_END);
  gtk_container_add (GTK_CONTAINER(event_box), (GtkWidget*)im);

  gtk_widget_set_events(event_box,  
                   GDK_BUTTON_PRESS_MASK );

  g_signal_connect(event_box, "button_press_event",
		  G_CALLBACK(button_press_event), event_box);



  //window
  window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_default_size(GTK_WINDOW(window),1000+num_of_color*100,IMG_H+400);
  g_signal_connect(window,"destroy",G_CALLBACK(quit),NULL);

  //button
  zoom=gtk_button_new_with_mnemonic("_Zoom out");
  g_signal_connect(zoom,"clicked",G_CALLBACK(zoom_out),NULL);



  //packing box
  vbox=gtk_vbox_new(FALSE,FALSE);

  out_hbox=gtk_hbox_new(FALSE,FALSE);
  right_vbox=gtk_vbox_new(FALSE,FALSE);

  //legend_box
  int i,j,k;
  for ( i=0; i<num_of_color; i++){
    GdkPixbuf *temp_buf=gdk_pixbuf_new(GDK_COLORSPACE_RGB,FALSE,8,40,40);
    g_object_ref(temp_buf);
	fill_pixbuf(i,temp_buf);
    legend_box[i]=gtk_hbox_new(FALSE,FALSE);
    gtk_box_pack_start(GTK_BOX(legend_box[i]), gtk_image_new_from_pixbuf(temp_buf),1,1,0);
	gtk_box_pack_start(GTK_BOX(right_vbox), legend_box[i],1,1,0);
    g_object_unref(temp_buf);
    char str[20];
    sprintf(str, "%d ~ %d", i*max_freq/num_of_color,(i+1)*max_freq/num_of_color);
    legend_label[i]=(GtkLabel*) gtk_label_new(str);
    gtk_box_pack_start(GTK_BOX(legend_box[i]), (GtkWidget*)legend_label[i],1,1,0);
  }

  gtk_box_pack_start(GTK_BOX(vbox),event_box,1,1,0);
//  gtk_box_pack_start_defaults(GTK_BOX(vbox),gtk_hseparator_new());

  char str1[10], str2[10];
  sprintf(str1,"%d",bg);
  left_label=(GtkLabel*)gtk_label_new(str1);

  sprintf(str2,"%d",ed);
  gtk_label_set_justify(left_label,GTK_JUSTIFY_LEFT);
  right_label=(GtkLabel*)gtk_label_new(str2);

  gtk_label_set_justify(right_label,GTK_JUSTIFY_RIGHT);



  bottom_label=(GtkLabel*)gtk_label_new(str1);


/*
  gtk_widget_set_halign(left_label,GTK_ALIGN_START);
  gtk_widget_set_valign(left_label,GTK_ALIGN_START);
  gtk_widget_set_halign(right_label,GTK_ALIGN_END);
  gtk_widget_set_valign(right_label,GTK_ALIGN_START);

*/

  bot_hbox=gtk_hbox_new(FALSE,FALSE);
  gtk_box_pack_start(GTK_BOX(bot_hbox),(GtkWidget*)left_label,1,1,0);
  gtk_box_pack_start(GTK_BOX(bot_hbox),(GtkWidget*)right_label,1,1,0);

  gtk_box_pack_start(GTK_BOX(vbox),bot_hbox,1,1,0);
  //gtk_box_pack_start_defaults(GTK_BOX(vbox),gtk_hseparator_new());
  gtk_box_pack_start(GTK_BOX(out_hbox),vbox,1,1,0);
  gtk_box_pack_start(GTK_BOX(out_hbox),right_vbox,1,1,0);


  gtk_container_add(GTK_CONTAINER(window),out_hbox);


  gtk_widget_show_all(window);
  gtk_main();

}

void test(){
	srand(time(NULL));
	ULL i;
	max_freq=0;
	for (i=0; i<len_chrom; i++){
		chrom_data[i]=rand() %12413;
		max_freq=max_freq<chrom_data[i] ? chrom_data[i] : max_freq;
//		printf("%llu \n",i);
//		dna_strand[i]='A';
	}
}

void init_color_map(){
	//color 0
	d2_color_map[0][0]=0;
	d2_color_map[0][1]=255;
	d2_color_map[0][2]=0;

	//color 1
	d2_color_map[1][0]=30;
	d2_color_map[1][1]=210;
	d2_color_map[1][2]=20;

    //color 2

	d2_color_map[2][0]=60;
	d2_color_map[2][1]=160;
	d2_color_map[2][2]=40;

    //color 3

	d2_color_map[3][0]=100;
	d2_color_map[3][1]=100;
	d2_color_map[3][2]=30;

    //color 4

	d2_color_map[4][0]=150;
	d2_color_map[4][1]=60;
	d2_color_map[4][2]=20;


    //color 5

	d2_color_map[5][0]=225;
	d2_color_map[5][1]=0;
	d2_color_map[5][2]=0;

	for (int i=0; i<num_of_color; i++)
		for (int j=0; j<3; j++)
			color_map[i*3+j]=d2_color_map[i][j];
		


}



void init_param(){
	len_chrom=150000000;
	chrom_data=(ULL*)malloc(sizeof(ULL)*len_chrom);
	bg=0; 
	ed=len_chrom-1;
	st.top=-1;
	init_color_map();
	zoomed_to_bottom=0;
	d_num_byte=sizeof(ULL)*len_chrom;
	r_num_byte=sizeof(unsigned char)*IMG_W*3;
	map_num_byte=num_of_color*3*sizeof(unsigned char);
	test();

	init_mem();

}


int main(int argc,char *argv[])
{

	gtk_init(&argc,&argv);
	init_param();
	set_up_window();
	return 0;
}
